import { http } from '@kit.NetworkKit';
import Request from '@system.request';
import { preferences } from '@kit.ArkData';
import { preferenceManager } from '../preference_manager/PreferenceManager';
import { composeLogger, loginLogger } from '../DebugManager/DebugManager';
import { StringUtil } from '../util/StringUtil';
import { logger } from '../logger/Logger';
import { BaseError, BaseErrorCode } from '../error_manager/BaseError';

export interface HttpManagerCallback {
  onDataSendProgress(sendSize:number,totalSize:number);
  onDataReceiveProgress(receiveSize:number,totalSize:number);
  onSetCookie(cookies:string);
}
export interface HttpResp<T extends RespBody> {
  code: number
  headers: object;
  body?: T | ArrayBuffer | string
}

export enum RequestMethod {
  GET,
  POST
}

export interface HttpReq<T extends ReqData> {
  url: string,
  method: RequestMethod,
  headers?: object
  body?: T | ArrayBuffer | string
}


/**
 * 返回的二进制数据流,如何转化为用户要得到的数据.
 */
export enum RespOutputType {
  Raw, //返回二进制数据流
  String, //二进制数据流 ==> 字符串
  JsonToObj, // 二进制数据流 ==> json字符串 ==> obj
}

/**
 * 用户输入的数据,如何填充到HttpRequest里面去.
 */
export enum ReqInputType {
  ObjToJson, //obj 转换为 json数据
  Raw, // 直接使用用户传进来的数据,不转换.
}

export interface RequestConfig<T> {
  url: string,
  method?: RequestMethod;
  expectDataType?: http.HttpDataType;
  headers?: Record<string, string>;
  body?: string | T
  respOutputType: RespOutputType
  reqInputType: number
}

export interface HttpManagerConfig {
  //是否把cookie保存到preference中.
  CookiePersistence:boolean
}
const DefaultHttpManagerConfig:HttpManagerConfig = {
  CookiePersistence:true
}


/**
 * 代表一个响应的返回数据
 */
export interface RespBody {}

export interface ReqData {}

const CookieKey = "cookie";
/**
 * 负责管理多次连续的请求
 * 1,cookie的保存,headers不返回给上层,全部在这里处理.
 * 请求执行层
 */

export class HttpManager {

  cookies:string
  callback?:HttpManagerCallback
  httpManagerConfig:HttpManagerConfig

  constructor(config?:HttpManagerConfig) {
    if(!config) {
      config = DefaultHttpManagerConfig
    }
    this.httpManagerConfig = config;
  }

  //cookie的持久化
  async loadCookie(): Promise<string> {
    this.cookies = await preferenceManager.fetchString(CookieKey);
    composeLogger.debug(`cookie is ${this.cookies}`)
    return this.cookies
  }

  /**
   * 什么事好的http请求框架?
   * 一次请求,叫做一次commucicate,有来有回.
   *
   * 1,网络请求结果本身是一个二进制数据流,要如何处理,自己手动指定.
   *
   * string,则返回二进制流的utf8字符串.
   * JsonToObj,将解析为字符串,然后解析为obj返回.
   *
   * 对原生http请求的第一层封装.
   *
   *
   * Req,是自己认为的标准的HttpReq结构.
   *
   * 用户输入 ==> StdReq ==> NativeReq
   * 从而实现StdReq的全适配,只需在不同环境中,增加 用户输入==> StdReq, StdReq==>NativeReq,自己的认知体系,就可以适应所有环境.
   */
  async communicate<Q, P extends RespBody>(config: RequestConfig<Q>): Promise<P | ArrayBuffer | string> {
    let req = this.reqFromConfig(config);

    //设置真实请求体.
    let httpResp: HttpResp<ArrayBuffer> = await this.nativeCommunicate(req);
    let buffer = httpResp.body as ArrayBuffer;
    // 转换为P中要求的resp数据.
    switch (config.respOutputType) {
      case RespOutputType.Raw: {
        return buffer;
      }
      case RespOutputType.String: {
        // 会有性能问题? 使用系统的进行优化?
        let resultStr = StringUtil.uint8ArrayToString(new Uint8Array(buffer));
        return resultStr;
      }
      case RespOutputType.JsonToObj: {
        let resultStr = StringUtil.uint8ArrayToString(new Uint8Array(buffer));
        let resultObj = JSON.parse(resultStr) as P
        return resultObj;
      }
    }
  }

  /**
   * 调用系统的请求协议去请求.
   * req中需要是完整的数据.
   * 这一层只返回二进制数据.
   * 系统返回的result是3种,string,object,ArrayBuffer.
   * @param config
   * @returns
   */
  async nativeCommunicate<Q>(req: HttpReq<Q>): Promise<HttpResp<ArrayBuffer>> {
    let url = req.url;
    let method = http.RequestMethod.POST;
    if (req?.method) {
      method = this.customRequestMethodToNative(req.method);
    }
    //让系统不解析,全部交给请求框架来处理.
    let expectDataType = http.HttpDataType.ARRAY_BUFFER;

    // if (req?.expectDataType) {
    //   expectDataType = config.expectDataType;
    // }
    // let extraData = config?.extraData;
    const reqOption: http.HttpRequestOptions = {
      header: req?.headers,
      extraData: req.body, // 请求体.
      method,
      expectDataType,
    }

    const httpRequest = http.createHttp();
    const response: HttpResp<ArrayBuffer> = {
      code: -1,
      headers: {},
      body: undefined
    }
    // http.HttpResponse 对象的set-cookie的值不是原始数据，也没有文档说明，这里只能自己获取
    httpRequest.on('headersReceive', (headers: Object) => {
      logger.debug('data headers', JSON.stringify(headers));
      response.headers = headers;
    });
    try {
      const httpResponse = await httpRequest.request(url, reqOption)
        .catch((e: unknown) => {
          let err = e as Error;
          logger.error(err.message);
          throw err;
        })

      //解析响应头
      let cookies = httpResponse.header["set-cookie"] as string[];
      if (cookies && cookies.length > 0) {
        for (const cookie of cookies) {
          const [key, value] = cookie.split(";")[0].split("=");
          this.setCookie(key, value);
        }
      }
      //这里得到的cookies是经历处理过的. #HttpOnly_.icoremail.net	TRUE	/	FALSE	0	Coremail	7a848f7373a18d8fa16e3dfa36f0a2fa
      let cookies2 = httpResponse.cookies;
      loginLogger.debug(`[response add cookies: ${cookies}]`);

      //解析响应体

      if (httpResponse.resultType == http.HttpDataType.STRING) {
        throw new BaseError();
      } else if (httpResponse.resultType == http.HttpDataType.ARRAY_BUFFER) {
        response.body = httpResponse.result as ArrayBuffer;
        return response;
      } else {
        throw new BaseError();
        // const text = `${url} ${req.body} return type error`
        // logger.error(text, resp.result);
        // const error = new BaseError(text, BaseErrorCode.PROTOCOL_ERROR, resp.result);
        // return Promise.reject(error);
      }
    } catch (e) {
      const text = `${url} ${req.body} request failed`
      logger.error(text, e);
      const error = new BaseError(text, BaseErrorCode.PROTOCOL_ERROR, e);
      return Promise.reject(error);
    } finally {
      httpRequest.destroy();
    }
  }

  setCookie(key: string, value: string) {
    if (!this.cookies) {
      this.cookies = `${key}=${value}`;
    } else if (this.cookies.includes(key + "=")) {
      this.cookies = this.cookies.replace(
        new RegExp(`${key}=[^;]*`),
        `${key}=${value}`
      );
    } else {
      this.cookies += `; ${key}=${value}`;
    }
    if(this.callback) {
      this.callback.onSetCookie(this.cookies);
    }

    preferenceManager.save(CookieKey,this.cookies);
  }

  customRequestMethodToNative(custom: RequestMethod): http.RequestMethod {
    switch (custom) {
      case RequestMethod.GET:
        return http.RequestMethod.GET
      case RequestMethod.POST:
      default:
        return http.RequestMethod.POST
    }
  }

  /**
   * config面对用户输入
   * req,是真实的请求输入.
   * @param config
   * @returns
   */
  reqFromConfig<Q>(config: RequestConfig<Q>): HttpReq<Q> {
    let url = config.url;


    //添加cookie到每个header
    if (this.cookies) {
      config.headers["cookie"] = this.cookies;
    }
    composeLogger.debug(`[req auto add cookie] [${this.cookies}]`);
    let body = config.body;
    switch (config.reqInputType) {
      case ReqInputType.ObjToJson:
        break;
      case ReqInputType.Raw:
        break;
    }
    let req: HttpReq<Q> = {
      url: url,
      headers: config.headers,
      method: config.method ?? RequestMethod.POST,
      body: body
    }
    // let expectDataType = http.HttpDataType.STRING;
    // if (config?.expectDataType) {
    //   expectDataType = config.expectDataType;
    // }
    // let extraData = config?.extraData;
    // const reqOption: http.HttpRequestOptions = {
    //   header: config?.headers,
    //   extraData,
    //   method,
    //   expectDataType,
    // }
    return req;
  }
}